import StoreKit
public typealias ProductIdentifier = String
public typealias ProductsRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -> Void
public typealias TransactionCompletionHandler = (SKPaymentTransactionState, Error?) -> Void
extension Notification.Name {
    static let IAPHelperPurchaseNotification = Notification.Name("IAPHelperPurchaseNotification")    
}
open class IAPHelper: NSObject  {
    private let productIdentifiers: Set<ProductIdentifier>
    var purchasedProductIdentifiers: Set<ProductIdentifier> = []
    private var productsRequest: SKProductsRequest?
    private var productsRequestCompletionHandler: ProductsRequestCompletionHandler?
    private var transactionCompletionHandlers: [ProductIdentifier: TransactionCompletionHandler] = [:]
    public init(productIds: Set<ProductIdentifier>) {
        productIdentifiers = productIds
        let current = Date()
        for productIdentifier in productIdentifiers {
            if let date = UserDefaults.standard.object(forKey: productIdentifier) as? Date, current < date {
                purchasedProductIdentifiers.insert(productIdentifier)
                print("Previously purchased: \(productIdentifier)")
            } else {
                UserDefaults.standard.removeObject(forKey: productIdentifier)
                print("Not purchased: \(productIdentifier)")
            }
        }
        super.init()
        SKPaymentQueue.default().add(self)
    }
}
extension IAPHelper {
    public func requestProducts(_ completionHandler: @escaping ProductsRequestCompletionHandler) {
        productsRequest?.cancel()
        productsRequestCompletionHandler = completionHandler
        productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
        productsRequest!.delegate = self
        productsRequest!.start()
    }
    public func buyProduct(_ product: SKProduct, completionHandler: @escaping TransactionCompletionHandler) {
        guard IAPHelper.canMakePayments() else {
            return
        }
        if purchasedProductIdentifiers.contains(product.productIdentifier) {
            completionHandler(.purchased, nil)
            return
        }
        let payment = SKPayment(product: product)
        SKPaymentQueue.default().add(payment)
        transactionCompletionHandlers[product.productIdentifier] = completionHandler
    }
    public func isProductPurchased(_ productIdentifier: ProductIdentifier) -> Bool {
        return purchasedProductIdentifiers.contains(productIdentifier)
    }
    public class func canMakePayments() -> Bool {
        return SKPaymentQueue.canMakePayments()
    }
}
extension IAPHelper: SKProductsRequestDelegate {
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        print("Loaded list of products...")
        let products = response.products
        productsRequestCompletionHandler?(true, products)
        clearRequestAndHandler()
        for p in products {
            print("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)")
        }
    }
    public func request(_ request: SKRequest, didFailWithError error: Error) {
        print("Failed to load list of products.")
        print("Error: \(error.localizedDescription)")
        productsRequestCompletionHandler?(false, nil)
        clearRequestAndHandler()
    }
    private func clearRequestAndHandler() {
        productsRequest = nil
        productsRequestCompletionHandler = nil
    }
}
extension IAPHelper: SKPaymentTransactionObserver {
    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch (transaction.transactionState) {
            case .purchased:
                complete(transaction: transaction)
                break
            case .failed:
                fail(transaction: transaction)
                break
            case .restored:
                restore(transaction: transaction)
                break
            case .deferred:
                break
            case .purchasing:
                break
            @unknown default:
                break
            }
        }
    }
    public func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
        return true
    }
}
extension IAPHelper {
    private func complete(transaction: SKPaymentTransaction) {
        print("complete...")
        if let handler = transactionCompletionHandlers[transaction.payment.productIdentifier] {
            handler(SKPaymentTransactionState.purchased, nil)
            transactionCompletionHandlers[transaction.payment.productIdentifier] = nil
        }
        deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
        SKPaymentQueue.default().finishTransaction(transaction)
    }
    private func restore(transaction: SKPaymentTransaction) {
        guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
        print("restore... \(productIdentifier)")
        if let handler = transactionCompletionHandlers[transaction.payment.productIdentifier] {
            handler(SKPaymentTransactionState.restored, nil)
            transactionCompletionHandlers[transaction.payment.productIdentifier] = nil
        }
        deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
        SKPaymentQueue.default().finishTransaction(transaction)
    }
    private func fail(transaction: SKPaymentTransaction) {
        print("fail...")
        if let handler = transactionCompletionHandlers[transaction.payment.productIdentifier] {
            handler(SKPaymentTransactionState.failed, transaction.error)
            transactionCompletionHandlers[transaction.payment.productIdentifier] = nil
        }
        SKPaymentQueue.default().finishTransaction(transaction)
    }
    private func deliverPurchaseNotificationFor(identifier: String?) {
        guard let identifier = identifier else { return }
        NotificationCenter.default.post(name: .IAPHelperPurchaseNotification, object: identifier)
    }
}
